home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 1992 August / info-mac-1992.iso / Source / C / Dragonsmith / Dragonsmith 1.0b2 / DFilePaths.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-05-20  |  15.0 KB  |  538 lines  |  [TEXT/KAHL]

  1. /*
  2.     DFilePaths.c
  3.     
  4.     A Dragon plug-in developed using THINK C 5.0
  5.  
  6.     Places a list of the full pathnames of the files dropped on it into the clipboard (desk scrap).
  7.     
  8.     Copyright © 1992 by Paul M. Hoffman
  9.     Send feedback to paul.hoffman@um.cc.umich.edu
  10.     
  11.     This code may be freely used, altered, and distributed in any way you want as long as:
  12.         1.    It is GIVEN away rather than sold;
  13.         2.    This statement and the above copyright notice are left intact.
  14.  
  15.     NOTE:    You should set the "Stationery aware" SIZE bit for this — since it doesn't actually
  16.             do anything to the things dropped on it, we don't want the Finder to think it has to
  17.             make a copy of a stationery file before we get to see it!
  18.     
  19.     Created    12 Apr 1992            Typed in ad hoc, so I know it'll be full o' bugs!
  20.     Modified    14 Apr 1992    v0.1        Rewritten in a more modular and well-thought-out fashion
  21.                                     — only now it's stuck on a (gasp!) "syntax error"
  22.                         v0.2        First working version.  Tested to 91 files, 9K of pathnames
  23.                                     (which means that GrowByAndPoint works — gee!)
  24.                         v0.3        OOPSied
  25.             30 Apr 1992            Trying to get a colon at the end of the path for directories
  26.             01 May 1992            OK, I give up!
  27.             02 May 1992            So I didn't give up after all — FSpIsFile works OK now (it
  28.                                     even returns FALSE for volumes)
  29.                                 Unfortunately, GetFullPath screws up when you give it
  30.                                     an FSSpec to a volume!
  31.                         v0.4        Fixed (I think) — doing PBGetCatInfo on a volume will give
  32.                                     an ioParID of 1 — no thanks to Apple for their lousy
  33.                                     documentation of PBGetCatInfo in Inside Macintosh!
  34.                                     I hope this hack doesn't break!
  35.             11 May 1992            Wait … looks like an FSpec.parID == fsRtParID (i.e., 1) by
  36.                                     itself is enough to identify a volume without calling
  37.                                     PBGetCatInfo.  Why didn't I think of that before?
  38.                                 Began implementing menus (Apple, File, Edit, Options)
  39.                                 Lots o' bugs!
  40.             12 May 1992            Quoting options work but choosing 'Text Files Only' crashes
  41.                                     with a bus error in the RTS at the end of DoOptionsMenu
  42.             14 May 1992            Lowered expectations — options menu now contains just No
  43.                                     Quotes, Single Quotes, and Double Quotes
  44.             17 May 1992    v1.0b1    First release (limited)
  45.             19 May 1992    v1.1        Raised expectations again — implemented "Full Paths" and
  46.                                     "Text Files Only" settings (in Options menu) — and brought
  47.                                     back angled brackets, which only make sense if you can
  48.                                     get file names (as opposed to full paths).  And it all works!
  49.             20 May 1992    v1.1b1    Second release
  50.             
  51.     Possible enhancements        ¶    Make an About box (oops! — forgot)
  52.                             ¶    Improve error reporting
  53.                             ¶    Implement a settings resource in which options are stored.  An
  54.                                 'Opts' resource will exist in the application file and a Preferences
  55.                                 file.  If there is no Preferences file, we'll create it.  If it couldn't
  56.                                 be created for some reason, we'll use the 'Opts' resource from
  57.                                 the application (which won't be modified except maybe by clever
  58.                                 ResEditors).  The first time it's created, we'll stuff a copy of the
  59.                                 resource from the application into it.  Check the resource for
  60.                                 nonsensical values when the app starts.  Checksum?  But that
  61.                                 would prevent ResEdit hacking.  Unless a zero checksum
  62.                                 indicates it should be recalculated …
  63.                             ¶    Add an option to create a file with the paths/names instead of
  64.                                 copying them to the clipboard.  Also append to a file?
  65.                             ¶    Consider making a FileGroup class.  Methods would include an
  66.                                 iterator (ForEachFile), a qualified iterator (ForEachMatchingFile),
  67.                                 a sub-setting method (ExtractMatchingFiles), etc.
  68.                             ¶    Also consider a File class with Specify (a flexible Specify, unlike
  69.                                 TCL's fossilized CFile::Specify___ methods), GetName, GetAlias,
  70.                                 GetPath, GetSpec (again, generalized), etc. methods.
  71.                             ¶    Think of a better word for Finder objects (File, Directory, Folder,
  72.                                 and Volume are too specific — we need a generic word for any 1 of
  73.                                 these) — and make a class for it.
  74. */
  75.  
  76. #include    "Dragon.h"
  77. #include    "FileUtils.h"
  78. #include    <Files.h>
  79. #include    <Types.h>
  80.  
  81. #define    cRETURN    '\r'
  82. #define    cNIL            '\0'
  83. #define    cCOLON        ':'
  84.  
  85. #define    szMaxNameLength        32            // FSSpec file names can be up to 64 bytes, but we're
  86.                                         //     going to pretend MFS doesn't exist — and file
  87.                                         //     names under HFS can only be 32 bytes
  88. #define    szMaxPathLen        1000        // I have no idea whether this would ever be surpassed
  89. #define    szExtraRoom            400            // Extra bytes to minimize # of GrowByAndPoint's needed
  90. #define    szPathLenEstimate    70            // Estimated average path length
  91. #define    szGrowBy            szPathLenEstimate * 6
  92.  
  93. static char quotes[4][2] = { { cNIL, cNIL }, { '\'', '\'' }, { '"', '"' }, { '<', '>' } };
  94.  
  95. enum {
  96.     mApple = 128,
  97.     mFile,
  98.     mEdit,
  99.     mOptions
  100. };
  101.  
  102. enum {
  103.     iAbout = 1,
  104.     iLine1
  105. };
  106.  
  107. enum {
  108.     iQuit = 1
  109. };
  110.  
  111. enum {
  112.     iFullPaths = 1,
  113.     iLine2,
  114.     iNoQuotes,
  115.     iSingleQuotes,
  116.     iDoubleQuotes,
  117.     iAngleBrackets,
  118.     iLine3,
  119.     iTextFilesOnly
  120. };
  121.  
  122. short GetFileName (FSSpec *fsspec, char *name);
  123. long GetFullPath (FSSpec *fsspec, char *path, OSErr *err);
  124. unsigned short ReverseCopyP2CStr (unsigned char *pas, char *c);
  125. void ReverseCStr (char *str);
  126. Boolean FSpIsFile (FSSpec *fss);
  127. Boolean FSpIsVolume (FSSpec *fss);        // Bonus function
  128. void CheckOne (MenuHandle menu, short first, short last, short itemToCheck);
  129. Boolean ToggleMenuItem (MenuHandle menu, short item);
  130. Boolean ItemIsChecked (MenuHandle menu, short item);
  131.  
  132. class DFilePaths: Dragon {
  133.  
  134.     protected:
  135.         char            leftQuote;
  136.         char            rightQuote;
  137.         Boolean        textFilesOnly;
  138.         Boolean        fullPaths;
  139.         MenuHandle    appleMenu;
  140.         MenuHandle    fileMenu;
  141.         MenuHandle    editMenu;
  142.         MenuHandle    optionsMenu;
  143.         
  144.     public:
  145.                     DFilePaths (void);
  146.         virtual OSErr    ProcessDroppings (FSSpec **docs, long numDocs);
  147.     
  148.     protected:
  149.         virtual void    SetUpMenus (void);
  150.         virtual void    InitOptionsMenu (void);
  151.         virtual void    SetQuotesOption (short optionItem);
  152.         virtual void    ToggleTextFilesOnly (void);
  153.         virtual void    ToggleFullPaths (void);
  154.         virtual void    DoMenu (long menuItemCode);
  155.         virtual void    DoAppleMenu (short itemNum);
  156.         virtual void    DoFileMenu (short itemNum);
  157.         virtual void    DoEditMenu (short itemNum);
  158.         virtual void    DoOptionsMenu (short itemNum);
  159.         virtual void    DoAbout (void);
  160. };
  161.  
  162. DFilePaths::DFilePaths (void)
  163. {
  164.     autoQuit = FALSE;
  165.     textFilesOnly = TRUE;
  166.     fullPaths = TRUE;                    
  167. }
  168.  
  169. void DFilePaths::SetUpMenus (void)
  170. {
  171.     if (menusInstalled)            // Just in case … (see Dragon::SetUpMenus)
  172.         return;
  173.         
  174.     appleMenu = GetMenu (mApple);        // Standard Apple Menu
  175.     AddResMenu (appleMenu, 'DRVR');
  176.     InsertMenu (appleMenu, 0);
  177.     
  178.     fileMenu = GetMenu (mFile);
  179.     InsertMenu (fileMenu, 0);
  180.     
  181.     editMenu = GetMenu (mEdit);
  182.     InsertMenu (editMenu, 0);
  183.     
  184.     optionsMenu = GetMenu (mOptions);
  185.     InsertMenu (optionsMenu, 0);
  186.     InitOptionsMenu ();
  187.     
  188.     DrawMenuBar ();
  189.     menusInstalled = TRUE;
  190. }
  191.  
  192. void DFilePaths::InitOptionsMenu (void)
  193. {
  194.     // Initialize options menu (and set the options while we're at it)
  195.     // Start out with no quotes
  196.     SetQuotesOption (iNoQuotes);
  197.     CheckItem (optionsMenu, iTextFilesOnly, textFilesOnly);
  198.     CheckItem (optionsMenu, iFullPaths, fullPaths);
  199. }
  200.  
  201. void DFilePaths::SetQuotesOption (short optionItem)
  202. {
  203.     CheckOne (optionsMenu, iNoQuotes, iAngleBrackets, optionItem);
  204.     leftQuote = quotes [optionItem - iNoQuotes][0];
  205.     rightQuote = quotes [optionItem - iNoQuotes][1];
  206. }
  207.  
  208. void DFilePaths::ToggleTextFilesOnly (void)
  209. {
  210.     textFilesOnly = ToggleMenuItem (optionsMenu, iTextFilesOnly);
  211. }
  212.  
  213. void DFilePaths::ToggleFullPaths (void)
  214. {
  215.     fullPaths = ToggleMenuItem (optionsMenu, iFullPaths);
  216. }
  217.  
  218. void DFilePaths::DoMenu (long menuItemCode)
  219. {
  220.     short    menuID, itemNum;
  221.     long        ticks;
  222.     
  223.     menuID = menuItemCode >> 16;
  224.     itemNum = menuItemCode & 0xFFFF;
  225.     
  226.     switch (menuID) {
  227.         case mApple:
  228.             DoAppleMenu (itemNum);
  229.             break;
  230.         case mFile:
  231.             DoFileMenu (itemNum);
  232.             break;
  233.         case mEdit:
  234.             DoEditMenu (itemNum);
  235.             break;
  236.         case mOptions:
  237.             DoOptionsMenu (itemNum);
  238.             break;
  239.         default:
  240.             break;
  241.     }
  242.     Delay (2, &ticks);        // Wait a little to let the user see the hilited menu title
  243.     HiliteMenu (0);
  244. }
  245.  
  246. void DFilePaths::DoAppleMenu (short itemNum)
  247. {
  248.     Str255    itemStr;
  249.     
  250.     if (itemNum == iAbout)
  251.         DoAbout ();
  252.     else {
  253.         GetItem (appleMenu, itemNum, itemStr);
  254.         OpenDeskAcc (itemStr);
  255.     }
  256. }
  257.  
  258. void DFilePaths::DoFileMenu (short itemNum)
  259. {
  260.     StopRunning ();
  261. }
  262.  
  263. void DFilePaths::DoEditMenu (short itemNum)
  264. {
  265.     // We don't do anything with this menu …
  266. }
  267.  
  268. void DFilePaths::DoOptionsMenu (short itemNum)
  269. {
  270.     switch (itemNum) {
  271.         case iFullPaths:
  272.             ToggleFullPaths ();
  273.             break;
  274.         case iNoQuotes:
  275.         case iSingleQuotes:
  276.         case iDoubleQuotes:
  277.         case iAngleBrackets:
  278.             SetQuotesOption (itemNum);
  279.             break;
  280.         case iTextFilesOnly:
  281.             ToggleTextFilesOnly ();
  282.             break;
  283.         default:
  284.             break;
  285.     }
  286. }
  287.  
  288. void DFilePaths::DoAbout (void)
  289. {
  290.     // Don't do anything yet …
  291. }
  292.  
  293. OSErr DFilePaths::ProcessDroppings (FSSpec **docs, long numDocs)
  294. {
  295.     char            *threshold, *limit;
  296.     register char    *p;
  297.     OSErr        err = noErr;
  298.     long            blockLen, len = 0L, pathsLen = 0L, i;
  299.     Handle        pathsHndl;
  300.     FSSpec        *fss;
  301.     
  302.     // First, allocate a block big enough to contain a reasonable number of paths
  303.     //    — this may be in the application zone or in the "temporary memory" space
  304.     blockLen = szPathLenEstimate * numDocs + szMaxPathLen + szExtraRoom;
  305.     pathsHndl = (Handle) AnyHandle (blockLen);
  306.     if (pathsHndl == NULL)
  307.         return errAEEventNotHandled;
  308.     
  309.     // Now set up three pointers — 
  310.     //    p            is a scan pointer that begins by pointing at the first byte in the block
  311.     //    threshold        points to the byte szMaxPathLen bytes from the end of the block
  312.     //    limit            points to the last byte in the block
  313.         
  314.     p = *pathsHndl;
  315.     limit = p + blockLen - 1;
  316.     threshold = limit - szMaxPathLen;
  317.     
  318.     HLock ((Handle) docs);
  319.     HLock ((Handle) pathsHndl);
  320.     
  321.     // Now, go through the array of FSSpecs getting the name or full path for each
  322.     for (fss = *docs; numDocs--; fss++) {
  323.     
  324.         // Don't process non-text files unless "Text Files Only" is unchecked
  325.         err = noErr;
  326.         if (textFilesOnly == FALSE || FSpType (fss, &err) == 'TEXT') {
  327.             if (err != noErr)            // We may have gotten an error in FSpType
  328.                 continue;            //    — just go on to the next one if we did
  329.                 
  330.             // Stick a left quote in front, if appropriate
  331.             if (leftQuote) {
  332.                 *p++ = leftQuote;
  333.                 pathsLen++;
  334.             }
  335.             
  336.             // Now copy the file name or full path, depending on the setting of the "Full Paths" option
  337.             if (fullPaths) {
  338.                 len = GetFullPath (fss, p, &err);
  339.                 if (err != noErr)        // Just skip this one if we had any problems
  340.                     continue;
  341.             } else
  342.                 len = (long) GetFileName (fss, p);
  343.             p += len;
  344.             pathsLen += len;
  345.             
  346.             // Stick a right quote at the end, if appropriate
  347.             if (rightQuote) {
  348.                 *p++ = rightQuote;
  349.                 pathsLen++;
  350.             }
  351.             
  352.             // Add a return character and check to see if we're running out of room
  353.             //    in the block we allocated to hold the list of file names/paths
  354.             *p++ = cRETURN;
  355.             pathsLen++;
  356.             if (p > threshold) {
  357.             
  358.                 // Check to see if we've thrashed what lies beyond the allocated block
  359.                 if (p > limit) {
  360.                     err = errAEEventNotHandled;
  361.                     break;
  362.                 }
  363.                 
  364.                 // Grow the block and keep our place in it — this unlocks the block,
  365.                 //    resizes it, relocks the block, and returns a pointer to the same
  366.                 //    byte in the block where we were before
  367.                 p = GrowByAndPoint (pathsHndl, p, szGrowBy, &blockLen);
  368.                 
  369.                 // Reset limit and threshold to point to the appropriate places
  370.                 threshold = (limit = p + blockLen - 1) - szMaxPathLen;
  371.             }
  372.         }
  373.     }
  374.     *p = cNIL;
  375.     HUnlock ((Handle) docs);
  376.     DisposHandle ((Handle) docs);
  377.     
  378.     SetHandleSize (pathsHndl, pathsLen);
  379.     if (err == noErr && *pathsHndl != cNIL)            // Don't change the clipboard if there was an error
  380.         err = HandleToScrap (pathsHndl, 'TEXT');    //    or if no droppings were processed
  381.     HUnlock (pathsHndl);
  382.     DisposHandle (pathsHndl);
  383.     
  384.     return err;
  385. }
  386.  
  387. Dragon *CreateGDragon (void)
  388. {
  389.     return (Dragon *) new DFilePaths;
  390. }
  391.  
  392. short GetFileName (register FSSpec *fsspec, register char *name)
  393. {
  394.     short            len, count;
  395.     unsigned char        *p, *limit;
  396.     
  397.     p = (unsigned char *) fsspec->name;
  398.     limit = p + len;
  399.     count = len = *p++;
  400.     while (count-- != 0)
  401.         *name++ = *p++;
  402.     return len;
  403. }
  404.  
  405. long GetFullPath (FSSpec *fsspec, char *path, OSErr *err)
  406. {
  407.     short        volume = fsspec->vRefNum;
  408.     OSErr        fsErr = noErr;
  409.     CInfoPBRec    catinfo;
  410.     char            dirName[64];
  411.     long            retLen = 0L, len;
  412.     register char    *p = path;
  413.     
  414.     // All comments assume a  file whose full path is
  415.     //    HD:Fonts:Garamond
  416.     
  417.     // If fsspec doesn't designate a file (i.e., it's a volume or directory), we write a colon
  418.     if ( ! FSpIsFile (fsspec)) {
  419.         *p++ = cCOLON;
  420.         retLen++;
  421.     }
  422.     
  423.     // First, copy the file name backwards from a Pascal to a C string:
  424.     //    "\pGaramond"  becomes "dnomaraG"
  425.  
  426.     len = (long) ReverseCopyP2CStr (fsspec->name, p);
  427.     p += len;
  428.     retLen += len;
  429.     
  430.     if ( ! FSpIsVolume (fsspec)) {        // Don't do anything more if we've got a volume
  431.     
  432.         catinfo.dirInfo.ioVRefNum = volume;
  433.         catinfo.dirInfo.ioNamePtr = (StringPtr) dirName;
  434.         catinfo.dirInfo.ioDrParID = fsspec->parID;
  435.         
  436.         // Now copy the rest of the path, one level at a time, backwards:
  437.         //    "dnomaraG:stnoF:DH"
  438.         do {
  439.             catinfo.dirInfo.ioFDirIndex = -1;
  440.             catinfo.dirInfo.ioDrDirID = catinfo.dirInfo.ioDrParID;    // <= This is the key — go from the
  441.                                                         //    current folder to its parent
  442.             fsErr = PBGetCatInfoSync (&catinfo);
  443.             if (fsErr == noErr) {
  444.                 *p++ = cCOLON;
  445.                 retLen += 1 + (len = ReverseCopyP2CStr ((StringPtr) dirName, p));
  446.                 p += len;
  447.             } else {
  448.                 *err = fsErr;
  449.                 return retLen;
  450.             }
  451.         } while (catinfo.dirInfo.ioDrDirID != fsRtDirID);
  452.     }
  453.     
  454.     // Finally, reverse the string and return its length and any error that might have occurred
  455.     ReverseCStr (path);
  456.     *err = fsErr;
  457.     return retLen;
  458. }
  459.  
  460. unsigned short ReverseCopyP2CStr (register unsigned char *pas, register char *c)
  461. {
  462.     register short    i, len = *pas++;
  463.     
  464.     for (i = len, c += len; i > 0; i--)
  465.         *--c = *pas++;
  466.     c += len;
  467.     *c = cNIL;
  468.     return len;
  469. }
  470.  
  471. void ReverseCStr (register char *str)
  472. {
  473.     register short        n = 0L, i;
  474.     register char        t, *p1, *p2;
  475.     
  476.     p1 = p2 = str;
  477.     while (*p2++)
  478.         ;
  479.     p2--;
  480.     n = (p2 - p1) / 2;
  481.     for (i = n; i > 0; i--) {
  482.         t = *--p2;
  483.         *p2 = *p1;
  484.         *p1++ = t;
  485.     }
  486. }
  487.  
  488. Boolean FSpIsFile (FSSpec *fss)
  489. {
  490.     CInfoPBRec    catinfo;
  491.     OSErr        err;
  492.     
  493.     if (FSpIsVolume (fss))
  494.         return FALSE;
  495.     catinfo.hFileInfo.ioNamePtr = fss->name;
  496.     catinfo.hFileInfo.ioVRefNum = fss->vRefNum;
  497.     catinfo.hFileInfo.ioFDirIndex = 0;
  498.     catinfo.hFileInfo.ioDirID = fss->parID;
  499.     err = PBGetCatInfoSync (&catinfo);
  500.     return ! (catinfo.hFileInfo.ioFlAttrib & ioDirMask);        // Bit 4 indicates it's a folder (or volume)
  501. }
  502.  
  503. Boolean FSpIsVolume (FSSpec *fss)
  504. {
  505.     return (fss->parID == fsRtParID);        // I guess this is what this constant is for …
  506. }
  507.  
  508. void CheckOne (MenuHandle menu, register short first, register short last, register short itemToCheck)
  509. {
  510.     register short        i;
  511.     short            mark;
  512.     
  513.     if (itemToCheck < first || itemToCheck > last)
  514.         return;
  515.     for (i = first; i < itemToCheck; i++)
  516.         SetItemMark (menu, i, noMark);
  517.     SetItemMark (menu, itemToCheck, checkMark);
  518.     for (i = itemToCheck + 1; i <= last; i++)
  519.         SetItemMark (menu, i, noMark);
  520. }
  521.  
  522. Boolean ItemIsChecked (MenuHandle menu, short item)
  523. {
  524.     short        mark;
  525.     
  526.     GetItemMark (menu, item, &mark);
  527.     return (mark == checkMark);
  528. }
  529.  
  530. Boolean ToggleMenuItem (MenuHandle menu, short item)
  531. {
  532.     Boolean    newSetting;
  533.     
  534.     newSetting = ! ItemIsChecked (menu, item);
  535.     CheckItem (menu, item, newSetting);
  536.     return newSetting;
  537. }
  538.